package com.cloudbroker.bcs.common.configure;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.cloudbroker.bcs.common.configure.HSBCSZooKeeperConfigurator.HSBCSConfigReader;
import com.cloudbroker.bcs.common.configure.constants.ConfigureConstants;
import com.cloudbroker.bcs.common.configure.zookeeper.ZooKeeperConfigurationListener;
import com.cloudbroker.bcs.common.event.EventPublisher;
import com.cloudbroker.bcs.common.util.ConcurrentHashSet;
import com.cloudbroker.bcs.common.util.JSONUtil;
import com.cloudbroker.bcs.common.zookeeper.PathChildrenCacheEventListener;

public class HSBCSConfigZooKeeperWatcher {
    
    private static final Logger LOG = LoggerFactory.getLogger(HSBCSConfigZooKeeperWatcher.class);
    
    private HSBCSZooKeeperConfigurator zkConfigurator;
    
    private HSBCSZooKeeperConfigLoader configLoader;
    
    // 配置类别
    private String category;
    
    // 当前类别配置根路径
    private String categoryConfigRootPath;
    
    // 当前类别配置的读取器
    private HSBCSConfigReader configReader;
    
    // 内容监听
    private ConfigContentListener contentListener = new ConfigContentListener();
    
    // 租户监听
    private TenantsListener tenantsListener;
    
    // 当前监听/已知的所有租户ID
    private Set<String> tenantIds = new ConcurrentHashSet<String>();
    
    public HSBCSConfigZooKeeperWatcher(HSBCSZooKeeperConfigurator zkConfigurator, String category,
            boolean listenForTenants, HSBCSZooKeeperConfigLoader configLoader) {
        this.zkConfigurator = zkConfigurator;
        this.category = category;
        this.configLoader = configLoader;
        // 初始化
        init(listenForTenants);
    }
    
    private void init(boolean listenForTenants) {
        // 获取该类别的配置根路径
        categoryConfigRootPath = HSBCSZooKeeperConfigurator.getCategoryConfigurationRootPath(category);
        if (listenForTenants) {
            // 如果需要监听租户的增减，需要注册TenantsListener
            tenantsListener = new TenantsListener();
            zkConfigurator.getClient().watchPathChildren(categoryConfigRootPath, tenantsListener);
        }
        // 初始化该类别配置的根节点，也即noTenant配置
        initCategoryRoot();
    }
    
    public void dispose() {
        // 注销租户监听
        unregisterTenantsListener();
        // 注销内容监听
        unregisterContentListeners();
    }
    
    private void initCategoryRoot() {
        // 注册该类别根节点（也即noTenant）的内容监听
        getConfigReader().onRoot().registerListener(contentListener);
        // 读取该类别根节点的配置更新到本地
        JSONObject configuration = zkConfigurator.getHSBCSConfigReader(category).getConfiguration();
        configuration = mergeConfig(configuration);
        ConfigUtil.getNoTenantConfig().updateConfig(category, configuration);
    }
    
    private void unregisterContentListeners() {
        if (null != configReader) {
            // 注销该类别根节点的内容监听
            configReader.onRoot().unregisterListener(contentListener);
            // 注销该类别所有租户的内容监听
            for (String tenantId : tenantIds) {
                configReader.onPath(tenantId).unregisterListener(contentListener);
            }
        }
    }
    
    private void unregisterTenantsListener() {
        if (null != tenantsListener) {
            // 如果该watcher有tenantsListener，则将其注销
            zkConfigurator.getClient().unregisterPathChildrenWatch(categoryConfigRootPath);
        }
    }
    
    /**
     * 获取该类别当前已知的所有租户ID
     * @return
     */
    public Set<String> getTenantIds() {
        return tenantIds;
    }
    
    public void registerTenants(Collection<String> tenantIds) {
        for (String tenantId : tenantIds) {
            registerTenant(tenantId);
        }
    }
    
    public void unregisterTenants(Collection<String> tenantIds) {
        for (String tenantId : tenantIds) {
            unregisterTenant(tenantId);
        }
    }
    
    public boolean registerTenant(String tenantId) {
        // 尝试添加
        boolean added = tenantIds.add(tenantId);
        if (added) {
            // 如果本次完成添加才做操作，使该方法重入无害
            LOG.info("added tenant " + tenantId + " in category " + category);
            // 为租户子节点注册内容监听
            getConfigReader().onPath(tenantId).registerListener(contentListener);
            // 如果有configLoader，表示需要调用configLoader的注册租户方法以通知其他watcher
            if (null != configLoader) {
                configLoader.registerTenant(tenantId);
            }
        }
        return added;
    }
    
    public boolean unregisterTenant(String tenantId) {
        // 尝试删除
        boolean removed = tenantIds.remove(tenantId);
        if (removed) {
            // 如果本次完成删除才做操作，使该方法重入无害
            LOG.info("removed tenant " + tenantId + " in category " + category);
            // 为租户子节点注销内容监听
            getConfigReader().onPath(tenantId).unregisterListener(contentListener);
            // 如果有configLoader，表示需要调用configLoader的注销租户方法以通知其他watcher
            if (null != configLoader) {
                configLoader.unregisterTenant(tenantId);
            }
        }
        return removed;
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private JSONObject mergeConfig(JSONObject configuration) {
        JSONObject result = new JSONObject();
        Set<String> keys = configuration.keySet();
        for (String key : keys) {
            Object config = configuration.get(key);
            // 第一层是文件名，需要做合并
            if (config instanceof JSONObject) {
                JSONUtil.merge(result, (JSONObject) config);
            } else if (config instanceof Map) {
                JSONUtil.merge(result, (Map) config);
            } else {
                result.put(key, config);
            }
        }
        
        dealKeyGroupedConfig(result);
        return result;
    }
    
    private void dealKeyGroupedConfig(JSONObject configuration) {
        Set<String> resultKeys = new HashSet<String>(configuration.keySet());
        for (String key : resultKeys) {
            Object config = configuration.get(key);
            // 组配置以$分隔组标志和组内key，将其归组
            int delimitorIdx = key.indexOf(ConfigureConstants.CONFIG_GROUP_DELIMITOR);
            if (-1 != delimitorIdx) {
                String groupKey = key.substring(0, delimitorIdx);
                String inGroupKey = key.substring(delimitorIdx + 1);
                JSONObject group = configuration.getJSONObject(groupKey);
                if (null == group) {
                    group = new JSONObject();
                    configuration.put(groupKey, group);
                    LOG.info("add grouped config: " + groupKey);
                }
                group.put(inGroupKey, config);
            }
        }
    }
    
    private HSBCSConfigReader getConfigReader() {
        if (null == configReader) {
            // 获取configReader并缓存
            configReader = zkConfigurator.getHSBCSConfigReader(category);
        }
        return configReader;
    }
    
    private String extractTenantIdFromFullPath(String fullPath) {
        // 剥取tenantId：/categoryConfigRootPath/tenantId
        if (fullPath.startsWith(categoryConfigRootPath) && fullPath.length() > categoryConfigRootPath.length() + 1) {
            return fullPath.substring(categoryConfigRootPath.length() + 1);
        }
        return null;
    }
    
    private class TenantsListener implements PathChildrenCacheEventListener {
        
        @Override
        public void childPathAdded(String fullPath, byte[] data, Stat stat) {
            // 提取tenantId
            String tenantId = extractTenantIdFromFullPath(fullPath);
            if (null != tenantId && registerTenant(tenantId)) {
                // 如果可以提取出tenantId，注册tenant
                // 如果注册成功打印日志
                LOG.info("new tenant added: " + tenantId);
            }
        }
        
        @Override
        public void childPathUpdated(String fullPath, byte[] data, Stat stat) {
            // 委托给add方法
            childPathAdded(fullPath, data, stat);
        }
        
        @Override
        public void childPathRemoved(String fullPath, Stat stat) {
            // 提取tenantId
            String tenantId = extractTenantIdFromFullPath(fullPath);
            if (null != tenantId && unregisterTenant(tenantId)) {
                // 如果可以提取出tenantId，注销tenant
                // 如果注销成功打印日志
                LOG.info("tenant removed: " + tenantId);
            }
        }
        
        @Override
        public boolean isNeedRecursionWatch() {
            // 配置根路径下只有一层租户节点，无需递归监听
            return false;
        }
        
        @Override
        public String toString() {
            return getClass().getName();
        }
        
    }
    
    private class ConfigContentListener implements ZooKeeperConfigurationListener {
        
        @Override
        public void configurationChanged(JSONObject configuration, String fullPath, String... concernFlags) {
            try {
                // 从关注标识中提取tenantId
                String tenantId = concernFlags[1];
                // 因为配置存储有两层（file-config），先要合并成单层
                JSONObject mergedConfig = mergeConfig(configuration);
                // 获取对应的TenantConfig，更新类别的配置
                ConfigUtil.getTenantConfig(tenantId, false).updateConfig(category, mergedConfig);
                // 对特殊类别做特殊处理，例如缓存的连接池需要刷新
                handleSpecialCategory(category, tenantId, mergedConfig);
                LOG.info("configuration of [" + tenantId + ']' + category + " reloaded");
            } catch (Exception e) {
                LOG.error("configuration of " + category + " reload failed", e);
            }
        }
        
        private void handleSpecialCategory(String category, String tenantId, JSONObject configuration) {
            // 发布一个相应类别的ConfigReloaded事件
            // 这个TenantConfigContentListener不是由spring管理生命周期的，所以不使用@Trigger
            EventPublisher.publishEvent(null, this, category + HSBCSZooKeeperConfigurator.EVT_RELOAD_SUFFIX,
                    tenantId, true);
        }
        
        @Override
        public String toString() {
            return getClass().getName();
        }
        
    }
    
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("HSBCSConfigZooKeeperWatcher [category=").append(category).append("]");
        return builder.toString();
    }
    
}
